require 'net/https'
require "rexml/document"
require 'uri'

$api_version = '1.0'

# Class for Mu Api commands. UUID and JobId for most calls are remembered 
# but can be overridden when needed.
class Mu_Api
  attr_accessor :host, :docroot, :posted_uuid, :run_uuid, :job_id, :username, :password

  def initialize(host=ENV['MU_IP'], docroot="/api/v3" )
    @host = host
    @username  = (ENV['MU_USER'].nil?) ? "admin" : ENV['MU_USER']
    @password  = (ENV['MU_PASSWORD'].nil?) ? "admin" : ENV['MU_PASSWORD']
    @docroot = docroot
    puts "Created API object to :#{@host}"
  end

  # send request to api
  def request(doc,verbose=false)
    uri = URI.parse("https://#{@host}/")
    resp = response = "";
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    escaped = URI.escape(doc)
    verbose and puts "DOC: #{@docroot}/#{escaped}"
    http.start do |http|    
      req = Net::HTTP::Get.new("#{@docroot}/#{escaped}", {"User-Agent" => @username})
      req.basic_auth(@username, @password)
      verbose and puts "REQ: #{req}"
      response = http.request(req)
      resp = response.body
    end
    xmldoc = REXML::Document.new(resp)
    return xmldoc
  rescue SocketError
    raise "Host " + @host + " nicht erreichbar"
  rescue REXML::ParseException => e
    print "error parsing XML " + e.to_s
  end

  # Post the analysis/template object to the muserver.
  def post(doc)
    xmldoc = nil
    uri = URI.parse("https://#{@host}/")
    resp = response = "";
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    begin
      http.start do |http|
        req = Net::HTTP::Post.new("/api/v3/templates/import", {"User-Agent" => @username})
        req.basic_auth(@username, @password)
        req.body = doc.to_s
        response = http.request(req)
        resp = response.body
      end
      #  XML Document
      xmldoc = REXML::Document.new(resp)
      @posted_uuid = xmldoc.elements["//uuid"].text
    rescue SocketError
      raise "Host " + host + " nicht erreichbar"
    rescue REXML::ParseException => e
      puts "error parsing XML " + e.to_s
    end
    if (xmldoc.nil? || xmldoc.elements.nil?)
      puts resp
      return nil
    end
    return xmldoc
  end

  #  used by archive(get) to access report.zip, returns the size of the zipfile
  def download(doc, filename="tmp.zip")
    escaped = URI.escape(doc)
    resp = href = "";
    http = Net::HTTP.new(@host, 443)
    http.use_ssl = true
    puts "DOC: #{@docroot}/#{escaped}"
    http.start do |http|
      req = Net::HTTP::Get.new("#{@docroot}/#{escaped}", {"User-Agent" => @username})
      req.basic_auth(@username, @password)
      resp = http.request(req)
      if resp.body.include?("Could not download archive")
        return false
      end
      open(filename, "wb") { |f|
        f.write(resp.body)
      }
      return File.size(filename)
    end
  end

  # For any of the possible status values, returns a list of analysis
  def list_by_status(status)
    uuid_list = Array.new()
    doc = request("analysis/list?status=#{status}")
    unless doc.elements["//analysis"].nil?
      doc.elements.each('*/analysis') { |e| uuid_list << e.attributes['uuid'] }
      return uuid_list
    end
  end

  # Get the status of a particular analysis, reference is the uuid
  def status(uuid=@run_uuid)
    doc = request("analysis/status?uuid=#{uuid}")
    unless doc.elements["//analysis"].nil?
      status = doc.elements["//status"].text
      return status
    end
    return doc
  end

  # Run an analysis, reference is the posted uuid. A name parameter can
  # be included so that each successive run of the same uuid yields a 
  # new name
  # ex: run(1234-1234-1234-1234, "new_name")
  def run(uuid=@posted_uuid, rename="")
    req = "analysis/run?uuid=#{uuid}"
    unless rename.empty?
      req = "#{req}&rename=#{rename}"
    end
    doc = request(req)
    unless doc.elements["//analysis"].nil?
      @run_uuid = doc.elements["//analysis"].attributes['uuid']
      return @run_uuid
    end
    return doc
  end

  # abort a running analysis. Next queued analysis will start
  def stop(uuid=@run_uuid)
    doc = request("analysis/stop?uuid=#{uuid}")
    unless doc.elements["//analysis"].nil?
      status = doc.elements["//analysis"].attributes['status']
      return status
    end
    return false
  end

  # pause a running analysis. Note that any queued analysis will NOT begin
  def pause(uuid=@run_uuid)
    doc = request("analysis/pause?uuid=#{uuid}")
    unless doc.elements["//analysis"].nil?
      status = doc.elements["//analysis"].attributes['status']
      return status
    end
    return false
  end

  # anti-pause
  def resume(uuid=@run_uuid)
    doc = request("analysis/resume?uuid=#{uuid}")
    unless doc.elements["//analysis"].nil?
      status = doc.elements["//analysis"].attributes['status']
      return status
    end
    return false
  end

  # delete analysis or template of any type
  def delete(uuid=@run_uuid)
    doc = request("analysis/delete?uuid=#{uuid}")
    unless doc.elements["//analysis"].nil?
      status = doc.elements["//analysis"].attributes['status']
      return status
    end
    return true
  end

  # return a list of faults (if any) for the analysis
  def get_faults(uuid=@run_uuid)
    doc = request("templates/export?uuid=#{uuid}")
    unless doc.elements["//analysis"].nil?
      faults = request("analysis/getFaultList?uuid=#{uuid}",true)
      return faults
    end
    return "error: no analysis with uuid: #{uuid} found"  
  end

  # return the name of an analysis referenced by uuid
  def get_name(uuid=@run_uuid)
    doc = request("templates/export?uuid=#{uuid}")
    unless doc.elements["//analysis"].nil?
      return doc.elements["//analysis"].attribute('name').value
    end
    return
  end

  # list all templates of type
  def list(type="analysis", templates_only='')
    case templates_only
    when "true"
      template_param = "?templatesOnly=true"
    when "false"
      template_param = "?templatesOnly=false"
    else
      template_param = ""
    end
    doc = request("#{type}/list#{template_param}")
    return doc
  end

  # archive has a set of three commands that are used to
  # generate and download an archive of all data produced by
  # a particular analysis
  # ex:
  # * achive(run)
  # * archive(status)
  # * archive(get) if archive(status) == "Finished"
  def archive(command="run",id=@run_uuid)
    case command
    when 'run'
      doc = request("archive/run?uuid=#{id}")
      unless doc.elements["//job"].nil?
        @job_id = doc.elements["//job"].attribute('id').value
        return @job_id
      end
      return doc
    when 'status'
      id = @job_id if id == @run_uuid
      puts id
      doc = request("archive/status?jobId=#{id}")
      unless doc.elements["//status"].nil?
        status = doc.elements["//status"].text
        return status
      end
      return doc
    when 'get'
      id = @job_id if id == @run_uuid
      download("archive/get?jobId=#{id}","#{id}.zip")
      return "#{id}.zip"
    end
    return false
  end

end
